﻿//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Mapping Application Block
//===============================================================================

using System;
using System.Data;
using System.Reflection;
using System.Configuration;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using EntLibContrib.Mapping.Instrumentation;
using EntLibContrib.Mapping.Configuration;
using EntLibContrib.Mapping.Properties;

namespace EntLibContrib.Mapping.Mappers
{
	/// <summary>
	/// A SuperMapper provides a conversion between multiple domain objects and a data transfer object.
	/// where the data transfer object contains a column that holds the domain object super type name or a code
	/// that relates to the super type name.
	/// </summary>
	/// <remarks>This mapper is used for classes that have super types like Dog and Cat to the base Animal class.
	/// You may perhaps have an Animal table in your database that contains a column to specify if any particular
	/// row is a dog or a cat. A SuperMapper can then use this information to instantiate Dog and Cat domain
	/// objects.</remarks>
	[ConfigurationElementType(typeof(SuperMapperData))]
	public class SuperMapper : MapperBase
	{
		#region Constants
		private MapperType mapperType = MapperType.Super;
		#endregion

		#region Fields
		private string domainObjectSuperTypeColumn;
		private DomainObjectSuperTypeNameDictionary domainObjectSuperTypeNames;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the type of the mapper.
		/// </summary>
		/// <value>The type of the mapper.</value>
		public override MapperType MapperType
		{
			get { return mapperType; }
		}

		/// <summary>
		/// Gets the domain object super type column in the data transfer object.
		/// </summary>
		/// <value>The domain object super type column.</value>
		public string DomainObjectSuperTypeColumn
		{
			get { return domainObjectSuperTypeColumn; }
		}

		/// <summary>
		/// Gets the Domain Object Super Type Name Dictionary.
		/// </summary>
		/// <value>A dictionary of Domain Object Super Type Names.</value>
		public DomainObjectSuperTypeNameDictionary DomainObjectSuperTypeNames
		{
			get { return domainObjectSuperTypeNames; }
		}
		#endregion

		#region Construction
		/// <summary>
		/// Initializes a new default instance of the <see cref="SuperMapper"/> class.
		/// </summary>
		/// <param name="name">The mapper name.</param>
		/// <param name="tableName">The data transfer object table name.</param>
		/// <param name="domainObjectSuperTypeColumn">The domain object super type column.</param>
		/// <param name="domainObjectSuperTypeNames">The domain object super type names.</param>
		/// <param name="maps">The maps.</param>
		/// <param name="instrumentationProvider">The instrumentation provider.</param>
		public SuperMapper(string name,
											string tableName,
											string domainObjectSuperTypeColumn,
											DomainObjectSuperTypeNameDictionary domainObjectSuperTypeNames,
											MapDictionary maps, 
											MappingInstrumentationProvider instrumentationProvider)
			: base(name, tableName, maps, instrumentationProvider)
		{
			if (string.IsNullOrEmpty(domainObjectSuperTypeColumn))
				throw new ArgumentNullException("domainObjectSuperTypeColumn");

			this.domainObjectSuperTypeColumn = domainObjectSuperTypeColumn;
			if (domainObjectSuperTypeNames == null)
				this.domainObjectSuperTypeNames = new DomainObjectSuperTypeNameDictionary();
			else
				this.domainObjectSuperTypeNames = domainObjectSuperTypeNames;
		}
		#endregion

		#region Protected Methods
		/// <summary>
		/// Creates a new populated domain object.
		/// </summary>
		/// <typeparam name="T">The domain object type</typeparam>
		/// <param name="dataTransferTableRow">The data transfer table row containing the domain object property values.</param>
		/// <returns>a populated domain object</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		protected override T CreateDomainObject<T>(DataRow dataTransferTableRow)
		{
			// get the type information
			string domainObjectTypeName = string.Empty;
			if (dataTransferTableRow[domainObjectSuperTypeColumn] == null)
				return null;
			else
			{
				if (domainObjectSuperTypeNames.ContainsKey(dataTransferTableRow[domainObjectSuperTypeColumn].ToString()))
					domainObjectTypeName = domainObjectSuperTypeNames[dataTransferTableRow[domainObjectSuperTypeColumn].ToString()];
				else
					domainObjectTypeName = dataTransferTableRow[domainObjectSuperTypeColumn].ToString();
			}
			if (string.IsNullOrEmpty(domainObjectTypeName))
				return null;

			Type domainObjectType = Type.GetType(domainObjectTypeName);
			// attempt to load the assembly containing the type if not already loaded
			Assembly domainAssembly = Assembly.Load(domainObjectType.Assembly.FullName);
			// attempt to create an instance of the domain object type
			T domainObject = (T)domainAssembly.CreateInstance(domainObjectType.FullName, true);
			// fill the domain object properties
			FillDomainObject<T>(domainObject, dataTransferTableRow);
			// return the populated object
			return domainObject;
		}

		/// <summary>
		/// Initialises the data transfer object columns.
		/// </summary>
		/// <typeparam name="T">The domain object type</typeparam>
		/// <param name="domainObject">The domain object.</param>
		/// <param name="dataTransferTable">The data transfer table.</param>
		/// <remarks>The columns for the data transfer table are derived from the domain object properties and the configured mappings</remarks>
		protected override void InitializeDataTransferObjectColumns<T>(T domainObject, DataTable dataTransferTable)
		{
			if (!dataTransferTable.Columns.Contains(domainObjectSuperTypeColumn))
				dataTransferTable.Columns.Add(domainObjectSuperTypeColumn, typeof(string));

			base.InitializeDataTransferObjectColumns<T>(domainObject, dataTransferTable);
		}

		/// <summary>
		/// Creates a single data transfer object row and fills it with data.
		/// </summary>
		/// <typeparam name="T">The domain object type</typeparam>
		/// <param name="domainObject">The domain object.</param>
		/// <param name="dataTransferTable">The initialised data transfer table.</param>
		/// <remarks>The table row data is derived from the domain object properties</remarks>
		protected override void FillDataTransferObjectRow<T>(T domainObject, DataTable dataTransferTable)
		{
			base.FillDataTransferObjectRow<T>(domainObject, dataTransferTable);
			// find the last row in the table
			DataRow dataTransferTableRow = dataTransferTable.Rows[dataTransferTable.Rows.Count - 1];
			// populate the super type column
			string domainObjectTypeName = domainObject.GetType().AssemblyQualifiedName;
			bool isListed = false;
			foreach(KeyValuePair<string, string> domainObjectSuperTypeName in domainObjectSuperTypeNames)
			{
				if (domainObjectSuperTypeName.Value == domainObjectTypeName)
				{
					dataTransferTableRow[domainObjectSuperTypeColumn] = domainObjectSuperTypeName.Key;
					isListed = true;
					break;
				}
			}
			if (!isListed)
				dataTransferTableRow[domainObjectSuperTypeColumn] = domainObjectTypeName;
		}
		#endregion
	}
}
